import os
import logging
from pathlib import Path
from typing import Any, Optional, Dict, Union
import yaml

class YamlConfigLoader:
    """
    YAML 配置加载工具类

    功能特性：
    1. 自动合并多配置文件实现配置分层
    2. 支持环境变量覆盖配置
    3. 类型安全的配置值获取
    4. 智能路径处理与配置缓存
    5. 详细的错误日志记录
    """

    def __init__(self, 
                 config_path: Union[str, Path, list] = "config.yml",
                 env_prefix: str = "APP_CFG"):
        """
        初始化配置加载器
        
        :param config_path: 配置文件路径（支持单个文件或文件列表）
        :param env_prefix: 环境变量前缀（如 "APP_CFG_DB_HOST"）
        """
        self._raw_config: Dict[str, Any] = {}
        self._env_prefix = env_prefix.upper()
        self.config_paths = self._parse_config_paths(config_path)
        self.reload()

    def _parse_config_paths(self, path_input) -> list:
        """将输入路径转换为 Path 对象列表"""
        if isinstance(path_input, (str, Path)):
            return [Path(path_input)]
        return [Path(p) for p in path_input]

    def reload(self) -> None:
        """重新加载所有配置文件"""
        merged_config = {}
        for path in self.config_paths:
            try:
                conf = self._load_single_file(path)
                merged_config = self._merge_configs(merged_config, conf)
            except Exception as e:
                logging.error(f"加载配置文件 {path} 失败: {str(e)}")
        self._raw_config = merged_config
        self._apply_env_overrides()

    @staticmethod
    def _load_single_file(path: Path) -> dict:
        """加载单个配置文件"""
        if not path.exists():
            raise FileNotFoundError(f"配置文件不存在: {path}")
        
        with open(path, 'r', encoding='utf-8') as f:
            return yaml.safe_load(f) or {}

    def _merge_configs(self, base: dict, update: dict) -> dict:
        """深度合并两个字典"""
        for key, value in update.items():
            if isinstance(value, dict) and key in base:
                base[key] = self._merge_configs(base.get(key, {}), value)
            else:
                base[key] = value
        return base

    def _apply_env_overrides(self) -> None:
        """应用环境变量覆盖"""
        for env_key, env_value in os.environ.items():
            if env_key.startswith(self._env_prefix):
                config_key = env_key[len(self._env_prefix)+1:].lower()
                keys = config_key.split('__')  # 使用双下划线分割层级
                self._set_nested_value(keys, env_value)

    def _set_nested_value(self, keys: list, value: Any) -> None:
        """递归设置嵌套配置值"""
        current = self._raw_config
        for key in keys[:-1]:
            current = current.setdefault(key, {})
        last_key = keys[-1]
        current[last_key] = self._parse_env_value(value)

    @staticmethod
    def _parse_env_value(value: str) -> Any:
        """将环境变量字符串转换为合适类型"""
        try:
            return yaml.safe_load(value)  # 自动转换类型
        except yaml.YAMLError:
            return value

    def get(self, 
           key_path: str, 
           default: Any = None,
           expected_type: type = None) -> Any:
        """
        安全获取配置值
        
        :param key_path: 使用点号分隔的配置路径 (例如 "database.host")
        :param default: 默认值（当配置不存在时返回）
        :param expected_type: 预期类型（进行类型校验）
        :return: 配置值或默认值
        """
        keys = key_path.split('.')
        value = self._raw_config
        
        try:
            for k in keys:
                value = value[k]
        except (KeyError, TypeError):
            return default

        # 类型校验
        if expected_type and not isinstance(value, expected_type):
            logging.warning(
                f"配置项 {key_path} 类型不符，预期 {expected_type}，实际 {type(value)}"
            )
            return default

        return value

    def get_path(self, key_path: str, base_dir: Path = None) -> Path:
        """
        获取路径配置并解析为绝对路径
        
        :param key_path: 配置路径
        :param base_dir: 基准目录（默认使用配置文件所在目录）
        :return: 解析后的 Path 对象
        """
        path_str = self.get(key_path, expected_type=str)
        if not path_str:
            return None

        if base_dir is None and self.config_paths:
            base_dir = self.config_paths[0].parent

        return (base_dir / path_str).resolve()

    def __str__(self) -> str:
        """安全显示配置（隐藏敏感信息）"""
        hidden_keys = {'password', 'secret', 'token'}
        return self._hide_sensitive(self._raw_config, hidden_keys)

    @staticmethod
    def _hide_sensitive(data: Any, hidden_keys: set) -> Any:
        """递归隐藏敏感信息"""
        if isinstance(data, dict):
            return {k: ('******' if k in hidden_keys else YamlConfigLoader._hide_sensitive(v, hidden_keys)) 
                    for k, v in data.items()}
        elif isinstance(data, list):
            return [YamlConfigLoader._hide_sensitive(item, hidden_keys) for item in data]
        return data

    @property
    def all_config(self) -> dict:
        """获取原始配置副本"""
        return self._raw_config.copy()